//
//  Log.cpp
//  sad
//
//  Created by Christopher Kemsley on 2/5/12.
//  Copyright (c) 2012 iAppolopolis. All rights reserved.
//

#include "Log.hpp"
#include <iostream>
#include <fstream>
#include <sstream>

#include "SystemCDefinitions.hpp"



#include "Settings.hpp"
#include "Codec.hpp"
#include "Log.hpp"




unsigned LogStream :: i ; // dummy variable



LogStream :: LogStream ( LogStream const & copy )
:	mStream			( copy.mStream )
,	mDeleteWhenDone	( copy.mDeleteWhenDone )
,	mRetainCount	( copy.mRetainCount )
,	mFormat			( copy.mFormat )
	{
	mRetainCount ++ ;
	}
LogStream :: ~LogStream ()
	{
	if ( mStream != 0 )
		{
		(*mStream) << std::flush ;
		mRetainCount-- ;
		if ( mRetainCount == 0 && mDeleteWhenDone )
			{
			delete mStream ;
			}
		}
	}

LogStream :: LogStream ()
:	mStream			( 0 )
,	mDeleteWhenDone	( false )
,	mRetainCount	( i )
,	mFormat			( kPreferredFormatTSV )
	{
	
	}
LogStream :: LogStream ( unsigned & inRetainCount , std::ostream * inStream , bool inDeleteWhenDone , PreferredFormat inFormat )
:	mStream			( inStream )
,	mDeleteWhenDone	( inDeleteWhenDone )
,	mRetainCount	( inRetainCount )
,	mFormat			( inFormat )
	{
	mRetainCount ++ ;
	}

LogStream :: PreferredFormat	LogStream :: preferredFormat () const
	{
	return mFormat ;
	}











struct	LogSettings : public Settings
{
	bool	showsErrors ;
	bool	showsWarnings ;
	bool	showsNotices ;
	bool	showsStates ;
	
	std::vector<std::string>	manualSources ;
	
	URL		logPath ;
	URL		stdPath ;
	URL		statsPathPrefix ;
	
	std::ostream*	logStream ;
	bool			ownsLogStream ;
	std::ostream*	stdStream ; // stdout
	bool			ownsStdStream ;
	
	LogSettings ( std::string const & name )
	:	Settings (name)
	,	showsErrors		( true )
	,	showsWarnings	( true )
	,	showsNotices	( true )
	,	showsStates		( false )
	,	logPath			()
	,	stdPath			()
	,	logStream		( new std::stringstream () )
	,	ownsLogStream	( true )
	,	stdStream		( new std::stringstream () )
	,	ownsStdStream	( true )
	,	statsPathPrefix	()
		{
		add ( "showErrors" , showsErrors ) ;
		add ( "showWarnings" , showsWarnings ) ;
		add ( "showNotices" , showsNotices ) ;
		add ( "showStates" , showsStates ) ;
		add ( "manualSources" , manualSources ) ;
		add ( "logPath" , logPath ) ;
		add ( "outPath" , stdPath ) ;
		add ( "statsPathPrefix" , statsPathPrefix ) ;
		}
	
	
	
	void	_updateStdErr ()
		{
		if ( ownsLogStream )
			delete logStream ;
		if ( logPath.path().size()==0 )
			{
			logStream = & std::cerr ;
			}
		else
			{
			std::ofstream * stream = new std::ofstream ( logPath.absolutePath().c_str() , std::ios_base::out ) ;
			if ( stream -> is_open() )
				{
				logStream = stream ;
				ownsLogStream = true ;
				}
			else
				{
				logStream = & std::cerr ;
				std::cerr << "Warning: failed to open log file\n" ;
				delete stream ;
				}
			}
		}
	void	_updateStdOut ()
		{
		if ( ownsStdStream )
			delete stdStream ;
		if ( stdPath == logPath )
			{
			stdStream = logStream ;
			ownsStdStream = false ;
			}
		else if ( stdPath.path().size()==0 )
			{
			stdStream = & std::cout ;
			}
		else
			{
			std::ofstream * stream = new std::ofstream ( stdPath.absolutePath().c_str() , std::ios_base::out ) ;
			if ( stream -> is_open() )
				{
				stdStream = stream ;
				ownsStdStream = true ;
				}
			else
				{
				stdStream = & std::cout ;
				std::cerr << "Warning: failed to open output file\n" ;
				delete stream ;
				}
			}
		}
	
	
	
	
	virtual void		validate ()
		{
		_updateStdErr () ;
		_updateStdOut () ;
		(*logStream) << "LogSettings\n" ;
		(*logStream) << "	Errors " << ( showsErrors ? "ARE" : "are NOT" ) << " shown\n" ;
		(*logStream) << "	Warnings " << ( showsWarnings ? "ARE" : "are NOT" ) << " shown\n" ;
		(*logStream) << "	Notices " << ( showsNotices ? "ARE" : "are NOT" ) << " shown\n" ;
		(*logStream) << "	States " << ( showsStates ? "ARE" : "are NOT" ) << " shown\n" ;
		(*logStream) << "	Error output: " << ( logPath.path().size()==0 ? "stderr" : logPath.absolutePath() ) << "\n" ;
		(*logStream) << "	Standard Output: " << ( stdPath.path().size()==0 ? "stdout" : stdPath.absolutePath() ) << "\n" ;
		(*logStream) << "	Statistics Output: " << ( statsPathPrefix.path().size()==0 ? "stdout" : statsPathPrefix.absolutePath() ) << "\n\n" ;
		}
	
	
	virtual bool	_doesAlwaysPrint ( std::string const & source ) const
		{
		std::vector<std::string>::const_iterator	here	=	manualSources.begin() ;
		std::vector<std::string>::const_iterator	end		=	manualSources.end() ;
		while ( here < end )
			if (  (*(here++))  ==  source  )
				return true ;
		return false ;
		}
} ;





void	Log ::	loadConfiguration	( URL & path )
	{
	Codec :: decode<URL,Settings> ( path , settings() ) ;
	}
LogSettings&	Log :: settings()
	{
	static LogSettings s ("Log") ;
	return s ;
	}



void Log ::	setShowsErrors		( bool value )
	{
	settings().showsErrors = value ;
	}
void Log ::	setShowsWarnings	( bool value )
	{
	settings().showsWarnings = value ;
	}
void Log ::	setShowsNotices		( bool value )
	{
	settings().showsNotices = value ;
	}
void Log ::	setShowsStates		( bool value )
	{
	settings().showsStates = value ;
	}

LogStream const	Log ::	error		( char const* domain )
	{
	if ( shared().mLogStreamReferenceCount != 0 )
		{
		ThrowException ( "Log" , "Log stream requested, but currently already checked out" , __FILE__,__LINE__ ) ;
		}
	
	if ( settings().showsErrors || settings()._doesAlwaysPrint(domain) )
		{
		LogStream s ( shared().mLogStreamReferenceCount , settings().logStream , false , LogStream::kPreferredFormatNatural ) ;
		s << "\n<" << SCCurrentTime().to_seconds() << " @ " << domain << "> Error\n	" ;
		return s ;
		}
	else
		{
		return LogStream () ;
		}
	}
LogStream const	Log ::	warning		( char const* domain )
	{
	if ( shared().mLogStreamReferenceCount != 0 )
		{
		ThrowException ( "Log" , "Log stream requested, but currently already checked out" , __FILE__,__LINE__ ) ;
		}
	
	if ( settings().showsWarnings || settings()._doesAlwaysPrint(domain) )
		{
		LogStream s ( shared().mLogStreamReferenceCount , settings().logStream , false , LogStream::kPreferredFormatNatural ) ;
		s << "\n<" << SCCurrentTime().to_seconds() << " @ " << domain << "> Warning\n	" ;
		return s ;
		}
	else
		{
		return LogStream () ;
		}
	}
LogStream const	Log ::	notice		( char const* domain )
	{
	if ( shared().mLogStreamReferenceCount != 0 )
		{
		ThrowException ( "Log" , "Log stream requested, but currently already checked out" , __FILE__,__LINE__ ) ;
		}
	
	if ( settings().showsNotices || settings()._doesAlwaysPrint(domain) )
		{
		LogStream s ( shared().mLogStreamReferenceCount , settings().logStream , false , LogStream::kPreferredFormatNatural ) ;
		s << "\n<" << SCCurrentTime().to_seconds() << " @ " << domain << "> Notice\n	" ;
		return s ;
		}
	else
		{
		return LogStream () ;
		}
	}
LogStream const	Log ::	states		( char const* domain )
	{
	if ( shared().mLogStreamReferenceCount != 0 )
		{
		ThrowException ( "Log" , "Log stream requested, but currently already checked out" , __FILE__,__LINE__ ) ;
		}
	
	if ( settings().showsStates || settings()._doesAlwaysPrint(domain) )
		{
		LogStream s ( shared().mLogStreamReferenceCount , settings().logStream , false , LogStream::kPreferredFormatNatural ) ;
		s << "\n<" << SCCurrentTime().to_seconds() << " @ " << domain << ">\n	" ;
		return s ;
		}
	else
		{
		return LogStream () ;
		}
	}
LogStream const	Log ::	output		()
	{
	if ( shared().mOutputStreamReferenceCount != 0 )
		{
		ThrowException ( "Log" , "Output stream requested, but currently already checked out" , __FILE__,__LINE__ ) ;
		}
	
	LogStream s ( shared().mOutputStreamReferenceCount , settings().stdStream , false , LogStream::kPreferredFormatNatural ) ;
	s << "\n" ;
	return s ;
	}
LogStream const	Log :: statistics	( std::string const & domain )
	{
	if ( shared().mStatsStreamReferenceCount != 0 )
		ThrowException ( "Log" , "Statistics stream requested, but currently already checked out" , __FILE__,__LINE__ ) ;
	
	if ( settings().statsPathPrefix.path().size() == 0 )
		{
		std::cout << "Statistics for " << domain << "\n" ;
		return LogStream ( shared().mStatsStreamReferenceCount , &std::cout , false , LogStream::kPreferredFormatNatural ) ;
		}
	else
		{
		URL path = settings().statsPathPrefix ;
		path . setPath ( path.path() + domain + ".txt"	) ;
		
		std::ofstream * stream = new std::ofstream ( path.absolutePath().c_str() , std::ios_base::out ) ;
		(*stream) << "Statistics for " << domain << "\n" ;
		return LogStream ( shared().mStatsStreamReferenceCount , stream , true , LogStream::kPreferredFormatTSV ) ;
		}
	}

void	Log :: flush		()
	{
	(*(settings().logStream)) << std::flush ;
	(*(settings().stdStream)) << std::flush ;
	}

void	Log :: logException	( Exception const& exception )
	{
	(*(settings().logStream)) << "\n\n" << exception << "\n\n" ;
	}


Log :: Log ()
:	mCurrent ( 0 )
,	mLogStreamReferenceCount ( 0 )
,	mOutputStreamReferenceCount ( 0 )
,	mStatsStreamReferenceCount ( 0 )
	{
	
	}
Log&	Log :: shared ()
	{
	static Log l ;
	return l ;
	}
